/* This file is part of libWiiSharp
 * Copyright (C) 2009 Leathl
 * 
 * libWiiSharp is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * libWiiSharp is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

//TPL conversion based on Wii.py by Xuzz, SquidMan, megazig, Matt_P, Omega and The Lemon Man.
//Zetsubou by SquidMan was also a reference.
//Thanks to the authors!

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;

namespace libWiiSharp
{
    #region Enums
    public enum TPL_TextureFormat : int
    {
        /// <summary>
        /// Intensity, 4bpp
        /// </summary>
        I4 = 0,
        /// <summary>
        /// Intensity, 8bpp
        /// </summary>
        I8 = 1,
        /// <summary>
        /// Intensity, Alpha, 8bpp
        /// </summary>
        IA4 = 2,
        /// <summary>
        /// Intensity, Alpha, 16bpp
        /// </summary>
        IA8 = 3,
        /// <summary>
        /// RGB, 16bpp
        /// </summary>
        RGB565 = 4,
        /// <summary>
        /// RGB, Alpha, 16bpp
        /// </summary>
        RGB5A3 = 5,
        /// <summary>
        /// RGB, Alpha, 32bpp
        /// </summary>
        RGBA8 = 6,
        /// <summary>
        /// Indexed, 4bpp
        /// </summary>
        CI4 = 8,
        /// <summary>
        /// Indexed, 8bpp
        /// </summary>
        CI8 = 9,
        /// <summary>
        /// Indexed, 14bpp
        /// </summary>
        CI14X2 = 10,
        /// <summary>
        /// S3TC Compressed
        /// </summary>
        CMP = 14,
    }

    public enum TPL_PaletteFormat : int
    {
        /// <summary>
        /// No Palette
        /// </summary>
        None = 255,
        /// <summary>
        /// Intensity, Alpha, 16bpp
        /// </summary>
        IA8 = 0,
        /// <summary>
        /// RGB, 16bpp
        /// </summary>
        RGB565 = 1,
        /// <summary>
        /// RGB, Alpha, 16bpp
        /// </summary>
        RGB5A3 = 2,
    }
    #endregion

    public class TPL : IDisposable
    {
        private TPL_Header tplHeader = new TPL_Header();
        private List<TPL_TextureEntry> tplTextureEntries = new List<TPL_TextureEntry>();
        private List<TPL_TextureHeader> tplTextureHeaders = new List<TPL_TextureHeader>();
        private List<TPL_PaletteHeader> tplPaletteHeaders = new List<TPL_PaletteHeader>();
        private List<byte[]> textureData = new List<byte[]>();
        private List<byte[]> paletteData = new List<byte[]>();

        /// <summary>
        /// The Number of Textures in the TPL.
        /// </summary>
        public int NumOfTextures { get { return (int)tplHeader.NumOfTextures; } }

		#region IDisposable Members
        private bool isDisposed = false;

        ~TPL()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing && !isDisposed)
            {
                tplHeader = null;

                tplTextureEntries.Clear();
                tplTextureEntries = null;

                tplTextureHeaders.Clear();
                tplTextureHeaders = null;

                tplPaletteHeaders.Clear();
                tplPaletteHeaders = null;

                textureData.Clear();
                textureData = null;

                paletteData.Clear();
                paletteData = null;
            }

            isDisposed = true;
        }
        #endregion

        #region Public Functions
        /// <summary>
        /// Loads a TPL file.
        /// </summary>
        /// <param name="pathToTpl"></param>
        /// <returns></returns>
        public static TPL Load(string pathToTpl)
        {
            TPL tmpTpl = new TPL();

            MemoryStream ms = new MemoryStream(File.ReadAllBytes(pathToTpl));

            try { tmpTpl.parseTpl(ms); }
            catch { ms.Dispose(); throw; }

            ms.Dispose();
            return tmpTpl;
        }

        /// <summary>
        /// Loads a TPL file.
        /// </summary>
        /// <param name="tplFile"></param>
        /// <returns></returns>
        public static TPL Load(byte[] tplFile)
        {
            TPL tmpTpl = new TPL();
            MemoryStream ms = new MemoryStream(tplFile);

            try { tmpTpl.parseTpl(ms); }
            catch { ms.Dispose(); throw; }

            ms.Dispose();
            return tmpTpl;
        }

        /// <summary>
        /// Loads a TPL file.
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static TPL Load(Stream tpl)
        {
            TPL t = new TPL();
            t.parseTpl(tpl);
            return t;
        }

        /// <summary>
        /// Creates a TPL from an image.
        /// Palette formats are only required for color indexed TPL formats (CI4, CI8 and CI14X2)!
        /// </summary>
        /// <param name="pathToImage"></param>
        /// <param name="tplFormat"></param>
        /// <returns></returns>
        public static TPL FromImage(string pathToImage, TPL_TextureFormat tplFormat, TPL_PaletteFormat paletteFormat = TPL_PaletteFormat.RGB5A3)
        {
            return FromImages(new string[] { pathToImage }, new TPL_TextureFormat[] { tplFormat }, new TPL_PaletteFormat[] { paletteFormat });
        }

        /// <summary>
        /// Creates a TPL from an image.
        /// Palette formats are only required for color indexed TPL formats (CI4, CI8 and CI14X2)!
        /// </summary>
        /// <param name="img"></param>
        /// <param name="tplFormat"></param>
        /// <returns></returns>
        public static TPL FromImage(Image img, TPL_TextureFormat tplFormat, TPL_PaletteFormat paletteFormat = TPL_PaletteFormat.RGB5A3)
        {
            return FromImages(new Image[] { img }, new TPL_TextureFormat[] { tplFormat }, new TPL_PaletteFormat[] { paletteFormat });
        }

        /// <summary>
        /// Creates a TPL from multiple images.
        /// Palette formats are only required for color indexed TPL formats (CI4, CI8 and CI14X2)!
        /// If you use a color indexed format, please provide at least one palette format.
        /// If you use multiple color indexed TPLs, the palette formats must have the same index as the image and tpl format!
        /// </summary>
        /// <param name="imagePaths"></param>
        /// <param name="tplFormats"></param>
        /// <returns></returns>
        public static TPL FromImages(string[] imagePaths, TPL_TextureFormat[] tplFormats, TPL_PaletteFormat[] paletteFormats)
        {
            if (tplFormats.Length < imagePaths.Length)
                throw new Exception("You must specify a format for each image!");

            List<Image> images = new List<Image>();
            foreach (string imagePath in imagePaths)
                images.Add(Image.FromFile(imagePath));

            TPL tmpTpl = new TPL();
            tmpTpl.createFromImages(images.ToArray(), tplFormats, paletteFormats);
            return tmpTpl;
        }

        /// <summary>
        /// Creates a TPL from multiple images.
        /// Palette formats are only required for color indexed TPL formats (CI4, CI8 and CI14X2)!
        /// If you use a color indexed format, please provide at least one palette format.
        /// If you use multiple color indexed TPLs, the palette formats must have the same index as the image and tpl format!
        /// </summary>
        /// <param name="images"></param>
        /// <param name="tplFormats"></param>
        /// <returns></returns>
        public static TPL FromImages(Image[] images, TPL_TextureFormat[] tplFormats, TPL_PaletteFormat[] paletteFormats)
        {
            if (tplFormats.Length < images.Length)
                throw new Exception("You must specify a format for each image!");

            TPL tmpTpl = new TPL();
            tmpTpl.createFromImages(images, tplFormats, paletteFormats);
            return tmpTpl;
        }



        /// <summary>
        /// Loads a TPL file.
        /// </summary>
        /// <param name="pathToTpl"></param>
        public void LoadFile(string pathToTpl)
        {
            MemoryStream ms = new MemoryStream(File.ReadAllBytes(pathToTpl));

            try { parseTpl(ms); }
            catch { ms.Dispose(); throw; }

            ms.Dispose();
        }

        /// <summary>
        /// Loads a TPL file.
        /// </summary>
        /// <param name="tplFile"></param>
        public void LoadFile(byte[] tplFile)
        {
            MemoryStream ms = new MemoryStream(tplFile);

            try { parseTpl(ms); }
            catch { ms.Dispose(); throw; }

            ms.Dispose();
        }

        /// <summary>
        /// Loads a TPL file.
        /// </summary>
        /// <param name="tpl"></param>
        public void LoadFile(Stream tpl)
        {
            parseTpl(tpl);
        }

        /// <summary>
        /// Creates a TPL from an image.
        /// Palette formats are only required for color indexed TPL formats (CI4, CI8 and CI14X2)!
        /// </summary>
        /// <param name="pathToImage"></param>
        /// <param name="tplFormat"></param>
        public void CreateFromImage(string pathToImage, TPL_TextureFormat tplFormat, TPL_PaletteFormat paletteFormat = TPL_PaletteFormat.RGB5A3)
        {
            CreateFromImages(new string[] { pathToImage }, new TPL_TextureFormat[] { tplFormat }, new TPL_PaletteFormat[] { paletteFormat });
        }

        /// <summary>
        /// Creates a TPL from an image.
        /// Palette formats are only required for color indexed TPL formats (CI4, CI8 and CI14X2)!
        /// </summary>
        /// <param name="img"></param>
        /// <param name="tplFormat"></param>
        public void CreateFromImage(Image img, TPL_TextureFormat tplFormat, TPL_PaletteFormat paletteFormat = TPL_PaletteFormat.RGB5A3)
        {
            CreateFromImages(new Image[] { img }, new TPL_TextureFormat[] { tplFormat }, new TPL_PaletteFormat[] { paletteFormat });
        }

        /// <summary>
        /// Creates a TPL from multiple images.
        /// Palette formats are only required for color indexed TPL formats (CI4, CI8 and CI14X2)!
        /// If you use a color indexed format, please provide at least one palette format.
        /// If you use multiple color indexed TPLs, the palette formats must have the same index as the image and tpl format!
        /// </summary>
        /// <param name="imagePaths"></param>
        /// <param name="tplFormats"></param>
        public void CreateFromImages(string[] imagePaths, TPL_TextureFormat[] tplFormats, TPL_PaletteFormat[] paletteFormats)
        {
            if (tplFormats.Length < imagePaths.Length)
                throw new Exception("You must specify a format for each image!");

            List<Image> images = new List<Image>();
            foreach (string imagePath in imagePaths)
                images.Add(Image.FromFile(imagePath));

            createFromImages(images.ToArray(), tplFormats, paletteFormats);
        }

        /// <summary>
        /// Creates a TPL from multiple images.
        /// Palette formats are only required for color indexed TPL formats (CI4, CI8 and CI14X2)!
        /// If you use a color indexed format, please provide at least one palette format.
        /// If you use multiple color indexed TPLs, the palette formats must have the same index as the image and tpl format!
        /// </summary>
        /// <param name="images"></param>
        /// <param name="tplFormats"></param>
        public void CreateFromImages(Image[] images, TPL_TextureFormat[] tplFormats, TPL_PaletteFormat[] paletteFormats)
        {
            if (tplFormats.Length < images.Length)
                throw new Exception("You must specify a format for each image!");

            createFromImages(images, tplFormats, paletteFormats);
        }



        /// <summary>
        /// Saves the TPL.
        /// </summary>
        /// <param name="savePath"></param>
        public void Save(string savePath)
        {
            if (File.Exists(savePath)) File.Delete(savePath);
            FileStream fs = new FileStream(savePath, FileMode.Create);

            try { writeToStream(fs); }
            catch { fs.Dispose(); throw; }

            fs.Dispose();
        }

        /// <summary>
        /// Returns the TPL as a memory stream.
        /// </summary>
        /// <returns></returns>
        public MemoryStream ToMemoryStream()
        {
            MemoryStream ms = new MemoryStream();

            try { writeToStream(ms); }
            catch { ms.Dispose(); throw; }

            return ms;
        }

        /// <summary>
        /// Returns the TPL as a byte array.
        /// </summary>
        /// <returns></returns>
        public byte[] ToByteArray()
        {
            return ToMemoryStream().ToArray();
        }

        /// <summary>
        /// Extracts the first Texture of the TPL.
        /// </summary>
        /// <returns></returns>
        public Image ExtractTexture()
        {
            return ExtractTexture(0);
        }

        /// <summary>
        /// Extracts the Texture with the given index.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public Image ExtractTexture(int index)
        {
            byte[] rgbaData;

            switch ((TPL_TextureFormat)tplTextureHeaders[index].TextureFormat)
            {
                case TPL_TextureFormat.I4:
                    rgbaData = fromI4(textureData[index], tplTextureHeaders[index].TextureWidth, tplTextureHeaders[index].TextureHeight);
                    break;
                case TPL_TextureFormat.I8:
                    rgbaData = fromI8(textureData[index], tplTextureHeaders[index].TextureWidth, tplTextureHeaders[index].TextureHeight);
                    break;
                case TPL_TextureFormat.IA4:
                    rgbaData = fromIA4(textureData[index], tplTextureHeaders[index].TextureWidth, tplTextureHeaders[index].TextureHeight);
                    break;
                case TPL_TextureFormat.IA8:
                    rgbaData = fromIA8(textureData[index], tplTextureHeaders[index].TextureWidth, tplTextureHeaders[index].TextureHeight);
                    break;
                case TPL_TextureFormat.RGB565:
                    rgbaData = fromRGB565(textureData[index], tplTextureHeaders[index].TextureWidth, tplTextureHeaders[index].TextureHeight);
                    break;
                case TPL_TextureFormat.RGB5A3:
                    rgbaData = fromRGB5A3(textureData[index], tplTextureHeaders[index].TextureWidth, tplTextureHeaders[index].TextureHeight);
                    break;
                case TPL_TextureFormat.RGBA8:
                    rgbaData = fromRGBA8(textureData[index], tplTextureHeaders[index].TextureWidth, tplTextureHeaders[index].TextureHeight);
                    break;
                case TPL_TextureFormat.CI4:
                    rgbaData = fromCI4(textureData[index], paletteToRgba(index), tplTextureHeaders[index].TextureWidth, tplTextureHeaders[index].TextureHeight);
                    break;
                case TPL_TextureFormat.CI8:
                    rgbaData = fromCI8(textureData[index], paletteToRgba(index), tplTextureHeaders[index].TextureWidth, tplTextureHeaders[index].TextureHeight);
                    break;
                case TPL_TextureFormat.CI14X2:
                    rgbaData = fromCI14X2(textureData[index], paletteToRgba(index), tplTextureHeaders[index].TextureWidth, tplTextureHeaders[index].TextureHeight);
                    break;
                case TPL_TextureFormat.CMP:
                    rgbaData = fromCMP(textureData[index], tplTextureHeaders[index].TextureWidth, tplTextureHeaders[index].TextureHeight);
                    break;
                default:
                    throw new FormatException("Unsupported Texture Format!");
            }

            return rgbaToImage(rgbaData, tplTextureHeaders[index].TextureWidth, tplTextureHeaders[index].TextureHeight);
        }

        /// <summary>
        /// Extracts the first Texture of the TPL.
        /// </summary>
        /// <param name="savePath"></param>
        public void ExtractTexture(string savePath)
        {
            ExtractTexture(0, savePath);
        }

        /// <summary>
        /// Extracts the Texture with the given index.
        /// </summary>
        /// <param name="index"></param>
        /// <param name="savePath"></param>
        public void ExtractTexture(int index, string savePath)
        {
            if (File.Exists(savePath)) File.Delete(savePath);
            Image img = ExtractTexture(index);

            switch (Path.GetExtension(savePath).ToLower())
            {
                case ".tif":
                case ".tiff":
                    img.Save(savePath, System.Drawing.Imaging.ImageFormat.Tiff);
                    break;
                case ".bmp":
                    img.Save(savePath, System.Drawing.Imaging.ImageFormat.Bmp);
                    break;
                case ".gif":
                    img.Save(savePath, System.Drawing.Imaging.ImageFormat.Gif);
                    break;
                case ".jpg":
                case ".jpeg":
                    img.Save(savePath, System.Drawing.Imaging.ImageFormat.Jpeg);
                    break;
                default:
                    img.Save(savePath, System.Drawing.Imaging.ImageFormat.Png);
                    break;
            }
        }

        /// <summary>
        /// Extracts all Textures of the TPL.
        /// </summary>
        /// <returns></returns>
        public Image[] ExtractAllTextures()
        {
            List<Image> imgList = new List<Image>();

            for (int i = 0; i < tplHeader.NumOfTextures; i++)
                imgList.Add(ExtractTexture(i));

            return imgList.ToArray();
        }

        /// <summary>
        /// Extracts all Textures of the TPL.
        /// </summary>
        /// <param name="saveDir"></param>
        public void ExtractAllTextures(string saveDir)
        {
            if (Directory.Exists(saveDir)) Directory.CreateDirectory(saveDir);

            for (int i = 0; i < tplHeader.NumOfTextures; i++)
                ExtractTexture(i, saveDir + Path.DirectorySeparatorChar + "Texture_" + i.ToString("x2") + ".png");
        }

        /// <summary>
        /// Adds a Texture to the TPL.
        /// </summary>
        /// <param name="imagePath"></param>
        /// <param name="tplFormat"></param>
        public void AddTexture(string imagePath, TPL_TextureFormat tplFormat, TPL_PaletteFormat paletteFormat = TPL_PaletteFormat.RGB5A3)
        {
            Image img = Image.FromFile(imagePath);
            AddTexture(img, tplFormat, paletteFormat);
        }

        /// <summary>
        /// Adds a Texture to the TPL.
        /// </summary>
        /// <param name="img"></param>
        /// <param name="tplFormat"></param>
        public void AddTexture(Image img, TPL_TextureFormat tplFormat, TPL_PaletteFormat paletteFormat = TPL_PaletteFormat.RGB5A3)
        {
            TPL_TextureEntry tempTexture = new TPL_TextureEntry();
            TPL_TextureHeader tempTextureHeader = new TPL_TextureHeader();
            TPL_PaletteHeader tempPaletteHeader = new TPL_PaletteHeader();
            byte[] tempTextureData = imageToTpl(img, tplFormat);
            byte[] tempPaletteData = new byte[0];

            tempTextureHeader.TextureHeight = (ushort)img.Height;
            tempTextureHeader.TextureWidth = (ushort)img.Width;
            tempTextureHeader.TextureFormat = (uint)tplFormat;

            if (tplFormat == TPL_TextureFormat.CI4 || tplFormat == TPL_TextureFormat.CI8 || tplFormat == TPL_TextureFormat.CI14X2)
            {
                ColorIndexConverter cic = new ColorIndexConverter(imageToRgba(img), img.Width, img.Height, tplFormat, paletteFormat);

                tempTextureData = cic.Data;
                tempPaletteData = cic.Palette;

                tempPaletteHeader.NumberOfItems = (ushort)(tempPaletteData.Length / 2);
                tempPaletteHeader.PaletteFormat = (uint)paletteFormat;
            }

            tplTextureEntries.Add(tempTexture);
            tplTextureHeaders.Add(tempTextureHeader);
            tplPaletteHeaders.Add(tempPaletteHeader);
            textureData.Add(tempTextureData);
            paletteData.Add(tempPaletteData);

            tplHeader.NumOfTextures++;
        }

        /// <summary>
        /// Removes a Texture from the TPL.
        /// </summary>
        /// <param name="index"></param>
        public void RemoveTexture(int index)
        {
            if (tplHeader.NumOfTextures > index)
            {
                tplTextureEntries.RemoveAt(index);
                tplTextureHeaders.RemoveAt(index);
                tplPaletteHeaders.RemoveAt(index);
                textureData.RemoveAt(index);
                paletteData.RemoveAt(index);

                tplHeader.NumOfTextures--;
            }
        }

        /// <summary>
        /// Gets the format of the Texture with the given index.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public TPL_TextureFormat GetTextureFormat(int index)
        {
            return (TPL_TextureFormat)tplTextureHeaders[index].TextureFormat;
        }

        /// <summary>
        /// Gets the palette format of the Texture with the given index.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public TPL_PaletteFormat GetPaletteFormat(int index)
        {
            return (TPL_PaletteFormat)tplPaletteHeaders[index].PaletteFormat;
        }

        /// <summary>
        /// Gets the size of the Texture with the given index.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public Size GetTextureSize(int index)
        {
            return new Size(tplTextureHeaders[index].TextureWidth, tplTextureHeaders[index].TextureHeight);
        }
        #endregion

        #region Private Functions
        private void writeToStream(Stream writeStream)
        {
            fireDebug("Writing TPL...");

            writeStream.Seek(0, SeekOrigin.Begin);

            fireDebug("   Writing TPL Header... (Offset: 0x{0})", writeStream.Position);
            tplHeader.Write(writeStream);

            int textureEntriesPosition = (int)writeStream.Position;
            writeStream.Seek(tplHeader.NumOfTextures * 8, SeekOrigin.Current);

            //Get Number of Palettes
            int paletteCount = 0;
            for (int i = 0; i < tplHeader.NumOfTextures; i++)
                if ((TPL_TextureFormat)tplTextureHeaders[i].TextureFormat == TPL_TextureFormat.CI4 ||
                    (TPL_TextureFormat)tplTextureHeaders[i].TextureFormat == TPL_TextureFormat.CI8 ||
                    (TPL_TextureFormat)tplTextureHeaders[i].TextureFormat == TPL_TextureFormat.CI14X2)
                    paletteCount++;

            int paletteHeaderPosition = (int)writeStream.Position;
            writeStream.Seek(paletteCount * 12, SeekOrigin.Current);

            //Write Palette Data
            for (int i = 0; i < tplHeader.NumOfTextures; i++)
            {
                if ((TPL_TextureFormat)tplTextureHeaders[i].TextureFormat == TPL_TextureFormat.CI4 ||
                    (TPL_TextureFormat)tplTextureHeaders[i].TextureFormat == TPL_TextureFormat.CI8 ||
                    (TPL_TextureFormat)tplTextureHeaders[i].TextureFormat == TPL_TextureFormat.CI14X2)
                {
                    fireDebug("   Writing Palette of Texture #{1}... (Offset: 0x{0})", writeStream.Position, i + 1);

                    writeStream.Seek(Shared.AddPadding(writeStream.Position, 32), SeekOrigin.Begin);

                    tplPaletteHeaders[i].PaletteDataOffset = (uint)writeStream.Position;
                    writeStream.Write(paletteData[i], 0, paletteData[i].Length);
                }
            }

            int textureHeaderPosition = (int)writeStream.Position;
            writeStream.Seek(tplHeader.NumOfTextures * 36, SeekOrigin.Current);

            //Write textureData
            for (int i = 0; i < tplHeader.NumOfTextures; i++)
            {
                fireDebug("   Writing Texture #{1} of {2}... (Offset: 0x{0})", writeStream.Position, i + 1, tplHeader.NumOfTextures);

                writeStream.Seek(Shared.AddPadding((int)writeStream.Position, 32), SeekOrigin.Begin);

                tplTextureHeaders[i].TextureDataOffset = (uint)writeStream.Position;
                writeStream.Write(textureData[i], 0, textureData[i].Length);
            }

            while (writeStream.Position % 32 != 0)
                writeStream.WriteByte(0x00);

            writeStream.Seek(paletteHeaderPosition, SeekOrigin.Begin);

            //Write Palette Headers
            for (int i = 0; i < tplHeader.NumOfTextures; i++)
            {
                if ((TPL_TextureFormat)tplTextureHeaders[i].TextureFormat == TPL_TextureFormat.CI4 ||
                    (TPL_TextureFormat)tplTextureHeaders[i].TextureFormat == TPL_TextureFormat.CI8 ||
                    (TPL_TextureFormat)tplTextureHeaders[i].TextureFormat == TPL_TextureFormat.CI14X2)
                {
                    fireDebug("   Writing Palette Header of Texture #{1}... (Offset: 0x{0})", writeStream.Position, i + 1);

                    tplTextureEntries[i].PaletteHeaderOffset = (uint)writeStream.Position;
                    tplPaletteHeaders[i].Write(writeStream);
                }
            }

            writeStream.Seek(textureHeaderPosition, SeekOrigin.Begin);

            //Write Texture Headers
            for (int i = 0; i < tplHeader.NumOfTextures; i++)
            {
                fireDebug("   Writing Texture Header #{1} of {2}... (Offset: 0x{0})", writeStream.Position, i + 1, tplHeader.NumOfTextures);

                tplTextureEntries[i].TextureHeaderOffset = (uint)writeStream.Position;
                tplTextureHeaders[i].Write(writeStream);
            }

            writeStream.Seek(textureEntriesPosition, SeekOrigin.Begin);

            //Write Texture Entries
            for (int i = 0; i < tplHeader.NumOfTextures; i++)
            {
                fireDebug("   Writing Texture Entry #{1} of {2}... (Offset: 0x{0})", writeStream.Position, i + 1, tplHeader.NumOfTextures);

                tplTextureEntries[i].Write(writeStream);
            }

            fireDebug("Writing TPL Finished...");
        }

        private void parseTpl(Stream tplFile)
        {
            fireDebug("Parsing TPL...");  

            tplHeader = new TPL_Header();
            tplTextureEntries = new List<TPL_TextureEntry>();
            tplTextureHeaders = new List<TPL_TextureHeader>();
            tplPaletteHeaders = new List<TPL_PaletteHeader>();
            textureData = new List<byte[]>();
            paletteData = new List<byte[]>();

            tplFile.Seek(0, SeekOrigin.Begin);
            byte[] temp = new byte[4];

            fireDebug("   Reading TPL Header: Magic... (Offset: 0x{0})", tplFile.Position);
            tplFile.Read(temp, 0, 4);
            if (Shared.Swap(BitConverter.ToUInt32(temp, 0)) != tplHeader.TplMagic)
            {
                fireDebug("    -> Invalid Magic: 0x{0}", Shared.Swap(BitConverter.ToUInt32(temp, 0)));
                throw new Exception("TPL Header: Invalid Magic!");
            }

            fireDebug("   Reading TPL Header: NumOfTextures... (Offset: 0x{0})", tplFile.Position);
            tplFile.Read(temp, 0, 4);
            tplHeader.NumOfTextures = Shared.Swap(BitConverter.ToUInt32(temp, 0));

            fireDebug("   Reading TPL Header: Headersize... (Offset: 0x{0})", tplFile.Position);
            tplFile.Read(temp, 0, 4);
            if (Shared.Swap(BitConverter.ToUInt32(temp, 0)) != tplHeader.HeaderSize)
            {
                fireDebug("    -> Invalid Headersize: 0x{0}", Shared.Swap(BitConverter.ToUInt32(temp, 0)));
                throw new Exception("TPL Header: Invalid Headersize!");
            }

            for (int i = 0; i < tplHeader.NumOfTextures; i++)
            {
                fireDebug("   Reading Texture Entry #{1} of {2}... (Offset: 0x{0})", tplFile.Position, i+1, tplHeader.NumOfTextures);

                TPL_TextureEntry tempTexture = new TPL_TextureEntry();

                tplFile.Read(temp, 0, 4);
                tempTexture.TextureHeaderOffset = Shared.Swap(BitConverter.ToUInt32(temp, 0));

                tplFile.Read(temp, 0, 4);
                tempTexture.PaletteHeaderOffset = Shared.Swap(BitConverter.ToUInt32(temp, 0));

                tplTextureEntries.Add(tempTexture);
            }

            for (int i = 0; i < tplHeader.NumOfTextures; i++)
            {
                fireDebug("   Reading Texture Header #{1} of {2}... (Offset: 0x{0})", tplFile.Position, i + 1, tplHeader.NumOfTextures);

                TPL_TextureHeader tempTextureHeader = new TPL_TextureHeader();
                TPL_PaletteHeader tempPaletteHeader = new TPL_PaletteHeader();
                tplFile.Seek(tplTextureEntries[i].TextureHeaderOffset, SeekOrigin.Begin);

                tplFile.Read(temp, 0, 4);
                tempTextureHeader.TextureHeight = Shared.Swap(BitConverter.ToUInt16(temp, 0));
                tempTextureHeader.TextureWidth = Shared.Swap(BitConverter.ToUInt16(temp, 2));

                tplFile.Read(temp, 0, 4);
                tempTextureHeader.TextureFormat = Shared.Swap(BitConverter.ToUInt32(temp, 0));

                tplFile.Read(temp, 0, 4);
                tempTextureHeader.TextureDataOffset = Shared.Swap(BitConverter.ToUInt32(temp, 0));

                tplFile.Read(temp, 0, 4);
                tempTextureHeader.WrapS = Shared.Swap(BitConverter.ToUInt32(temp, 0));

                tplFile.Read(temp, 0, 4);
                tempTextureHeader.WrapT = Shared.Swap(BitConverter.ToUInt32(temp, 0));

                tplFile.Read(temp, 0, 4);
                tempTextureHeader.MinFilter = Shared.Swap(BitConverter.ToUInt32(temp, 0));

                tplFile.Read(temp, 0, 4);
                tempTextureHeader.MagFilter = Shared.Swap(BitConverter.ToUInt32(temp, 0));

                tplFile.Read(temp, 0, 4);
                tempTextureHeader.LodBias = Shared.Swap(BitConverter.ToUInt32(temp, 0));

                tplFile.Read(temp, 0, 4);
                tempTextureHeader.EdgeLod = temp[0];
                tempTextureHeader.MinLod = temp[1];
                tempTextureHeader.MaxLod = temp[2];
                tempTextureHeader.Unpacked = temp[3];

                if (tplTextureEntries[i].PaletteHeaderOffset != 0)
                {
                    fireDebug("   Reading Palette Header #{1} of {2}... (Offset: 0x{0})", tplFile.Position, i + 1, tplHeader.NumOfTextures);

                    tplFile.Seek(tplTextureEntries[i].PaletteHeaderOffset, SeekOrigin.Begin);

                    tplFile.Read(temp, 0, 4);
                    tempPaletteHeader.NumberOfItems = Shared.Swap(BitConverter.ToUInt16(temp, 0));
                    tempPaletteHeader.Unpacked = temp[2];
                    tempPaletteHeader.Pad = temp[3];

                    tplFile.Read(temp, 0, 4);
                    tempPaletteHeader.PaletteFormat = Shared.Swap(BitConverter.ToUInt32(temp, 0));

                    tplFile.Read(temp, 0, 4);
                    tempPaletteHeader.PaletteDataOffset = Shared.Swap(BitConverter.ToUInt32(temp, 0));
                }

                tplFile.Seek(tempTextureHeader.TextureDataOffset, SeekOrigin.Begin);
                byte[] tempTextureData = new byte[textureByteSize((TPL_TextureFormat)tempTextureHeader.TextureFormat, tempTextureHeader.TextureWidth, tempTextureHeader.TextureHeight)];
                byte[] tempPaletteData = new byte[tempPaletteHeader.NumberOfItems * 2];

                fireDebug("   Reading Texture #{1} of {2}... (Offset: 0x{0})", tplFile.Position, i + 1, tplHeader.NumOfTextures);
                tplFile.Read(tempTextureData, 0, tempTextureData.Length);

                if (tplTextureEntries[i].PaletteHeaderOffset > 0)
                {
                    fireDebug("   Reading Palette #{1} of {2}... (Offset: 0x{0})", tplFile.Position, i + 1, tplHeader.NumOfTextures);

                    tplFile.Seek(tempPaletteHeader.PaletteDataOffset, SeekOrigin.Begin);
                    tplFile.Read(tempPaletteData, 0, tempPaletteData.Length);
                }
                else tempPaletteData = new byte[0];

                tplTextureHeaders.Add(tempTextureHeader);
                tplPaletteHeaders.Add(tempPaletteHeader);
                textureData.Add(tempTextureData);
                paletteData.Add(tempPaletteData);
            }
        }

        private int textureByteSize(TPL_TextureFormat tplFormat, int width, int height)
        {
            switch (tplFormat)
            {
                case TPL_TextureFormat.I4:
                    return Shared.AddPadding(width, 8) * Shared.AddPadding(height, 8) / 2;
                case TPL_TextureFormat.I8:
                case TPL_TextureFormat.IA4:
                    return Shared.AddPadding(width, 8) * Shared.AddPadding(height, 4);
                case TPL_TextureFormat.IA8:
                case TPL_TextureFormat.RGB565:
                case TPL_TextureFormat.RGB5A3:
                    return Shared.AddPadding(width, 4) * Shared.AddPadding(height, 4) * 2;
                case TPL_TextureFormat.RGBA8:
                    return Shared.AddPadding(width, 4) * Shared.AddPadding(height, 4) * 4;
                case TPL_TextureFormat.CI4:
                    return Shared.AddPadding(width, 8) * Shared.AddPadding(height, 8) / 2;
                case TPL_TextureFormat.CI8:
                    return Shared.AddPadding(width, 8) * Shared.AddPadding(height, 4);
                case TPL_TextureFormat.CI14X2:
                    return Shared.AddPadding(width, 4) * Shared.AddPadding(height, 4) * 2;
                case TPL_TextureFormat.CMP:
                    return width * height;
                default:
                    throw new FormatException("Unsupported Texture Format!");
            }
        }

        private void createFromImages(Image[] images, TPL_TextureFormat[] tplFormats, TPL_PaletteFormat[] paletteFormats)
        {
            tplHeader = new TPL_Header();
            tplTextureEntries = new List<TPL_TextureEntry>();
            tplTextureHeaders = new List<TPL_TextureHeader>();
            tplPaletteHeaders = new List<TPL_PaletteHeader>();
            textureData = new List<byte[]>();
            paletteData = new List<byte[]>();

            tplHeader.NumOfTextures = (uint)images.Length;

            for (int i = 0; i < images.Length; i++)
            {
                Image img = images[i];

                TPL_TextureEntry tempTexture = new TPL_TextureEntry();
                TPL_TextureHeader tempTextureHeader = new TPL_TextureHeader();
                TPL_PaletteHeader tempPaletteHeader = new TPL_PaletteHeader();
                byte[] tempTextureData = imageToTpl(img, tplFormats[i]);
                byte[] tempPaletteData = new byte[0];

                tempTextureHeader.TextureHeight = (ushort)img.Height;
                tempTextureHeader.TextureWidth = (ushort)img.Width;
                tempTextureHeader.TextureFormat = (uint)tplFormats[i];

                if (tplFormats[i] == TPL_TextureFormat.CI4 || tplFormats[i] == TPL_TextureFormat.CI8 || tplFormats[i] == TPL_TextureFormat.CI14X2)
                {
                    ColorIndexConverter cic = new ColorIndexConverter(imageToRgba(img), img.Width, img.Height, tplFormats[i], paletteFormats[i]);

                    tempTextureData = cic.Data;
                    tempPaletteData = cic.Palette;

                    tempPaletteHeader.NumberOfItems = (ushort)(tempPaletteData.Length / 2);
                    tempPaletteHeader.PaletteFormat = (uint)paletteFormats[i];
                }

                tplTextureEntries.Add(tempTexture);
                tplTextureHeaders.Add(tempTextureHeader);
                tplPaletteHeaders.Add(tempPaletteHeader);
                textureData.Add(tempTextureData);
                paletteData.Add(tempPaletteData);
            }
        }

        private byte[] imageToTpl(Image img, TPL_TextureFormat tplFormat)
        {
            switch (tplFormat)
            {
                case TPL_TextureFormat.I4:
                    return toI4((Bitmap)img);
                case TPL_TextureFormat.I8:
                    return toI8((Bitmap)img);
                case TPL_TextureFormat.IA4:
                    return toIA4((Bitmap)img);
                case TPL_TextureFormat.IA8:
                    return toIA8((Bitmap)img);
                case TPL_TextureFormat.RGB565:
                    return toRGB565((Bitmap)img);
                case TPL_TextureFormat.RGB5A3:
                    return toRGB5A3((Bitmap)img);
                case TPL_TextureFormat.RGBA8:
                    return toRGBA8((Bitmap)img);
                case TPL_TextureFormat.CI4:
                case TPL_TextureFormat.CI8:
                case TPL_TextureFormat.CI14X2:
                    return new byte[0];
                case TPL_TextureFormat.CMP:
                default:
                    throw new FormatException("Format not supported!\nCurrently, images can only be converted to the following formats:\nI4, I8, IA4, IA8, RGB565, RGB5A3, RGBA8, CI4, CI8 , CI14X2.");
            }
        }

        private uint[] imageToRgba(Image img)
        {
            Bitmap bmp = (Bitmap)img;
            System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
                System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

            byte[] pixelData = new byte[bmpData.Height * (int)Math.Abs(bmpData.Stride)];
            System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, pixelData, 0, pixelData.Length);

            bmp.UnlockBits(bmpData);
            return Shared.ByteArrayToUIntArray(pixelData);
        }

        private Bitmap rgbaToImage(byte[] data, int width, int height)
        {
            if (width == 0) width = 1;
            if (height == 0) height = 1;

            Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

            try
            {
                System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(
                                     new Rectangle(0, 0, bmp.Width, bmp.Height),
                                     System.Drawing.Imaging.ImageLockMode.WriteOnly, bmp.PixelFormat);

                System.Runtime.InteropServices.Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
                bmp.UnlockBits(bmpData);
            }
            catch { bmp.Dispose(); throw; }

            return bmp;
        }

        private uint[] paletteToRgba(int index)
        {
            TPL_PaletteFormat paletteformat = (TPL_PaletteFormat)tplPaletteHeaders[index].PaletteFormat;
            int itemcount = tplPaletteHeaders[index].NumberOfItems;
            int r, g, b, a;

            uint[] output = new uint[itemcount];
            for (int i = 0; i < itemcount; i++)
            {
                if (i >= itemcount) continue;

                ushort pixel = BitConverter.ToUInt16(new byte[] { paletteData[index][i * 2 + 1], paletteData[index][i * 2] }, 0);

                if (paletteformat == TPL_PaletteFormat.IA8) //IA8
                {
                    r = pixel & 0xff;
                    b = r;
                    g = r;
                    a = pixel >> 8;
                }
                else if (paletteformat == TPL_PaletteFormat.RGB565) //RGB565
                {
                    b = (((pixel >> 11) & 0x1F) << 3) & 0xff;
                    g = (((pixel >> 5) & 0x3F) << 2) & 0xff;
                    r = (((pixel >> 0) & 0x1F) << 3) & 0xff;
                    a = 255;
                }
                else //RGB5A3
                {
                    if ((pixel & (1 << 15)) != 0) //RGB555
                    {
                        a = 255;
                        b = (((pixel >> 10) & 0x1F) * 255) / 31;
                        g = (((pixel >> 5) & 0x1F) * 255) / 31;
                        r = (((pixel >> 0) & 0x1F) * 255) / 31;
                    }
                    else //RGB4A3
                    {
                        a = (((pixel >> 12) & 0x07) * 255) / 7;
                        b = (((pixel >> 8) & 0x0F) * 255) / 15;
                        g = (((pixel >> 4) & 0x0F) * 255) / 15;
                        r = (((pixel >> 0) & 0x0F) * 255) / 15;
                    }
                }

                output[i] = (uint)((r << 0) | (g << 8) | (b << 16) | (a << 24));
            }

            return output;
        }

        private int avg(int w0, int w1, int c0, int c1)
        {
            int a0 = c0 >> 11;
            int a1 = c1 >> 11;
            int a = (w0 * a0 + w1 * a1) / (w0 + w1);
            int c = (a << 11) & 0xffff;

            a0 = (c0 >> 5) & 63;
            a1 = (c1 >> 5) & 63;
            a = (w0 * a0 + w1 * a1) / (w0 + w1);
            c = c | ((a << 5) & 0xffff);

            a0 = c0 & 31;
            a1 = c1 & 31;
            a = (w0 * a0 + w1 * a1) / (w0 + w1);
            c = c | a;

            return c;
        }
        #endregion

        #region Conversions
        #region RGBA8
        private byte[] fromRGBA8(byte[] tpl, int width, int height)
        {
            uint[] output = new uint[width * height];
            int inp = 0;

            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 4)
                {
                    for (int k = 0; k < 2; k++)
                    {
                        for (int y1 = y; y1 < y + 4; y1++)
                        {
                            for (int x1 = x; x1 < x + 4; x1++)
                            {
                                ushort pixel = Shared.Swap(BitConverter.ToUInt16(tpl, inp++ * 2));

                                if ((x1 >= width) || (y1 >= height))
                                    continue;

                                if (k == 0)
                                {
                                    int a = (pixel >> 8) & 0xff;
                                    int r = (pixel >> 0) & 0xff;
                                    output[x1 + (y1 * width)] |= (uint)((r << 16) | (a << 24));
                                }
                                else
                                {
                                    int g = (pixel >> 8) & 0xff;
                                    int b = (pixel >> 0) & 0xff;
                                    output[x1 + (y1 * width)] |= (uint)((g << 8) | (b << 0));
                                }
                            }
                        }
                    }
                }
            }

            return Shared.UIntArrayToByteArray(output);
        }

        private byte[] toRGBA8(Bitmap img)
        {
            uint[] pixeldata = imageToRgba(img);
            int w = img.Width;
            int h = img.Height;
            int z = 0, iv = 0;
            byte[] output = new byte[Shared.AddPadding(w, 4) * Shared.AddPadding(h, 4) * 4];
            uint[] lr = new uint[32], lg = new uint[32], lb = new uint[32], la = new uint[32];

            for (int y1 = 0; y1 < h; y1 += 4)
            {
                for (int x1 = 0; x1 < w; x1 += 4)
                {
                    for (int y = y1; y < (y1 + 4); y++)
                    {
                        for (int x = x1; x < (x1 + 4); x++)
                        {
                            uint rgba;

                            if (y >= h || x >= w)
                                rgba = 0;
                            else
                                rgba = pixeldata[x + (y * w)];

                            lr[z] = (uint)(rgba >> 16) & 0xff;
                            lg[z] = (uint)(rgba >> 8) & 0xff;
                            lb[z] = (uint)(rgba >> 0) & 0xff;
                            la[z] = (uint)(rgba >> 24) & 0xff;

                            z++;
                        }
                    }

                    if (z == 16)
                    {
                        for (int i = 0; i < 16; i++)
                        {
                            output[iv++] = (byte)(la[i]);
                            output[iv++] = (byte)(lr[i]);
                        }
                        for (int i = 0; i < 16; i++)
                        {
                            output[iv++] = (byte)(lg[i]);
                            output[iv++] = (byte)(lb[i]);
                        }

                        z = 0;
                    }
                }
            }

            return output;
        }
        #endregion

        #region RGB5A3
        private byte[] fromRGB5A3(byte[] tpl, int width, int height)
        {
            uint[] output = new uint[width * height];
            int inp = 0;
            int r, g, b;
            int a = 0;

            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 4)
                {
                    for (int y1 = y; y1 < y + 4; y1++)
                    {
                        for (int x1 = x; x1 < x + 4; x1++)
                        {
                            ushort pixel = Shared.Swap(BitConverter.ToUInt16(tpl, inp++ * 2));

                            if (y1 >= height || x1 >= width)
                                continue;

                            if ((pixel & (1 << 15)) != 0)
                            {
                                b = (((pixel >> 10) & 0x1F) * 255) / 31;
                                g = (((pixel >> 5) & 0x1F) * 255) / 31;
                                r = (((pixel >> 0) & 0x1F) * 255) / 31;
                                a = 255;
                            }
                            else
                            {
                                a = (((pixel >> 12) & 0x07) * 255) / 7;
                                b = (((pixel >> 8) & 0x0F) * 255) / 15;
                                g = (((pixel >> 4) & 0x0F) * 255) / 15;
                                r = (((pixel >> 0) & 0x0F) * 255) / 15;
                            }

                            output[(y1 * width) + x1] = (uint)((r << 0) | (g << 8) | (b << 16) | (a << 24));
                        }
                    }
                }
            }

            return Shared.UIntArrayToByteArray(output);
        }

        private byte[] toRGB5A3(Bitmap img)
        {
            uint[] pixeldata = imageToRgba(img);
            int w = img.Width;
            int h = img.Height;
            int z = -1;
            byte[] output = new byte[Shared.AddPadding(w, 4) * Shared.AddPadding(h, 4) * 2];

            for (int y1 = 0; y1 < h; y1 += 4)
            {
                for (int x1 = 0; x1 < w; x1 += 4)
                {
                    for (int y = y1; y < y1 + 4; y++)
                    {
                        for (int x = x1; x < x1 + 4; x++)
                        {
                            int newpixel;

                            if (y >= h || x >= w)
                                newpixel = 0;
                            else
                            {
                                int rgba = (int)pixeldata[x + (y * w)];
                                newpixel = 0;

                                int r = (rgba >> 16) & 0xff;
                                int g = (rgba >> 8) & 0xff;
                                int b = (rgba >> 0) & 0xff;
                                int a = (rgba >> 24) & 0xff;

                                if (a <= 0xda) //RGB4A3
                                {
                                    newpixel &= ~(1 << 15);

                                    r = ((r * 15) / 255) & 0xf;
                                    g = ((g * 15) / 255) & 0xf;
                                    b = ((b * 15) / 255) & 0xf;
                                    a = ((a * 7) / 255) & 0x7;

                                    newpixel |= (a << 12) | (r << 8) | (g << 4) | b;
                                }
                                else //RGB5
                                {
                                    newpixel |= (1 << 15);

                                    r = ((r * 31) / 255) & 0x1f;
                                    g = ((g * 31) / 255) & 0x1f;
                                    b = ((b * 31) / 255) & 0x1f;

                                    newpixel |= (r << 10) | (g << 5) | b;
                                }
                            }

                            output[++z] = (byte)(newpixel >> 8);
                            output[++z] = (byte)(newpixel & 0xff);
                        }
                    }
                }
            }

            return output;
        }
        #endregion

        #region RGB565
        private byte[] fromRGB565(byte[] tpl, int width, int height)
        {
            uint[] output = new uint[width * height];
            int inp = 0;

            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 4)
                {
                    for (int y1 = y; y1 < y + 4; y1++)
                    {
                        for (int x1 = x; x1 < x + 4; x1++)
                        {
                            ushort pixel = Shared.Swap(BitConverter.ToUInt16(tpl, inp++ * 2));

                            if (y1 >= height || x1 >= width)
                                continue;

                            int b = (((pixel >> 11) & 0x1F) << 3) & 0xff;
                            int g = (((pixel >> 5) & 0x3F) << 2) & 0xff;
                            int r = (((pixel >> 0) & 0x1F) << 3) & 0xff;

                            output[y1 * width + x1] = (uint)((r << 0) | (g << 8) | (b << 16) | (255 << 24));
                        }
                    }
                }
            }

            return Shared.UIntArrayToByteArray(output);
        }

        private byte[] toRGB565(Bitmap img)
        {
            uint[] pixeldata = imageToRgba(img);
            int w = img.Width;
            int h = img.Height;
            int z = -1;
            byte[] output = new byte[Shared.AddPadding(w, 4) * Shared.AddPadding(h, 4) * 2];

            for (int y1 = 0; y1 < h; y1 += 4)
            {
                for (int x1 = 0; x1 < w; x1 += 4)
                {
                    for (int y = y1; y < y1 + 4; y++)
                    {
                        for (int x = x1; x < x1 + 4; x++)
                        {
                            ushort newpixel;

                            if (y >= h || x >= w)
                                newpixel = 0;
                            else
                            {
                                uint rgba = pixeldata[x + (y * w)];

                                uint b = (rgba >> 16) & 0xff;
                                uint g = (rgba >> 8) & 0xff;
                                uint r = (rgba >> 0) & 0xff;

                                newpixel = (ushort)(((b >> 3) << 11) | ((g >> 2) << 5) | ((r >> 3) << 0));
                            }

                            output[++z] = (byte)(newpixel >> 8);
                            output[++z] = (byte)(newpixel & 0xff);
                        }
                    }
                }
            }

            return output;
        }
        #endregion

        #region I4
        private byte[] fromI4(byte[] tpl, int width, int height)
        {
            uint[] output = new uint[width * height];
            int inp = 0;

            for (int y = 0; y < height; y += 8)
            {
                for (int x = 0; x < width; x += 8)
                {
                    for (int y1 = y; y1 < y + 8; y1++)
                    {
                        for (int x1 = x; x1 < x + 8; x1 += 2)
                        {
                            int pixel = tpl[inp++];

                            if (y1 >= height || x1 >= width)
                                continue;

                            int i = (pixel >> 4) * 255 / 15;
                            output[y1 * width + x1] = (uint)((i << 0) | (i << 8) | (i << 16) | (255 << 24));

                            i = (pixel & 0x0F) * 255 / 15;
                            if (y1 * width + x1 + 1 < output.Length) output[y1 * width + x1 + 1] = (uint)((i << 0) | (i << 8) | (i << 16) | (255 << 24));
                        }
                    }
                }
            }

            return Shared.UIntArrayToByteArray(output);
        }

        private byte[] toI4(Bitmap img)
        {
            uint[] pixeldata = imageToRgba(img);
            int w = img.Width;
            int h = img.Height;
            int inp = 0;
            byte[] output = new byte[Shared.AddPadding(w, 8) * Shared.AddPadding(h, 8) / 2];

            for (int y1 = 0; y1 < h; y1 += 8)
            {
                for (int x1 = 0; x1 < w; x1 += 8)
                {
                    for (int y = y1; y < y1 + 8; y++)
                    {
                        for (int x = x1; x < x1 + 8; x += 2)
                        {
                            byte newpixel;

                            if (x >= w || y >= h)
                                newpixel = 0;
                            else
                            {
                                uint rgba = pixeldata[x + (y * w)];

                                uint r = (rgba >> 0) & 0xff;
                                uint g = (rgba >> 8) & 0xff;
                                uint b = (rgba >> 16) & 0xff;

                                uint i1 = ((r + g + b) / 3) & 0xff;

                                if ((x + (y * w) + 1) >= pixeldata.Length) rgba = 0;
                                else rgba = pixeldata[x + (y * w) + 1];

                                r = (rgba >> 0) & 0xff;
                                g = (rgba >> 8) & 0xff;
                                b = (rgba >> 16) & 0xff;

                                uint i2 = ((r + g + b) / 3) & 0xff;

                                newpixel = (byte)((((i1 * 15) / 255) << 4) | (((i2 * 15) / 255) & 0xf));
                            }

                            output[inp++] = newpixel;
                        }
                    }
                }
            }

            return output;
        }
        #endregion

        #region I8
        private byte[] fromI8(byte[] tpl, int width, int height)
        {
            uint[] output = new uint[width * height];
            int inp = 0;

            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 8)
                {
                    for (int y1 = y; y1 < y + 4; y1++)
                    {
                        for (int x1 = x; x1 < x + 8; x1++)
                        {
                            int pixel = tpl[inp++];

                            if (y1 >= height || x1 >= width)
                                continue;

                            output[y1 * width + x1] = (uint)((pixel << 0) | (pixel << 8) | (pixel << 16) | (255 << 24));
                        }
                    }
                }
            }

            return Shared.UIntArrayToByteArray(output);
        }

        private byte[] toI8(Bitmap img)
        {
            uint[] pixeldata = imageToRgba(img);
            int w = img.Width;
            int h = img.Height;
            int inp = 0;
            byte[] output = new byte[Shared.AddPadding(w, 8) * Shared.AddPadding(h, 4)];

            for (int y1 = 0; y1 < h; y1 += 4)
            {
                for (int x1 = 0; x1 < w; x1 += 8)
                {
                    for (int y = y1; y < y1 + 4; y++)
                    {
                        for (int x = x1; x < x1 + 8; x++)
                        {
                            byte newpixel;

                            if (x >= w || y >= h)
                                newpixel = 0;
                            else
                            {
                                uint rgba = pixeldata[x + (y * w)];

                                uint r = (rgba >> 0) & 0xff;
                                uint g = (rgba >> 8) & 0xff;
                                uint b = (rgba >> 16) & 0xff;

                                newpixel = (byte)(((r + g + b) / 3) & 0xff);
                            }

                            output[inp++] = newpixel;
                        }
                    }
                }
            }

            return output;
        }
        #endregion

        #region IA4
        private byte[] fromIA4(byte[] tpl, int width, int height)
        {
            uint[] output = new uint[width * height];
            int inp = 0;

            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 8)
                {
                    for (int y1 = y; y1 < y + 4; y1++)
                    {
                        for (int x1 = x; x1 < x + 8; x1++)
                        {
                            int pixel = tpl[inp++];

                            if (y1 >= height || x1 >= width)
                                continue;

                            int i = ((pixel & 0x0F) * 255 / 15) & 0xff;
                            int a = (((pixel >> 4) * 255) / 15) & 0xff;

                            output[y1 * width + x1] = (uint)((i << 0) | (i << 8) | (i << 16) | (a << 24));
                        }
                    }
                }
            }

            return Shared.UIntArrayToByteArray(output);
        }

        private byte[] toIA4(Bitmap img)
        {
            uint[] pixeldata = imageToRgba(img);
            int w = img.Width;
            int h = img.Height;
            int inp = 0;
            byte[] output = new byte[Shared.AddPadding(w, 8) * Shared.AddPadding(h, 4)];

            for (int y1 = 0; y1 < h; y1 += 4)
            {
                for (int x1 = 0; x1 < w; x1 += 8)
                {
                    for (int y = y1; y < y1 + 4; y++)
                    {
                        for (int x = x1; x < x1 + 8; x++)
                        {
                            byte newpixel;

                            if (x >= w || y >= h)
                                newpixel = 0;
                            else
                            {
                                uint rgba = pixeldata[x + (y * w)];

                                uint r = (rgba >> 0) & 0xff;
                                uint g = (rgba >> 8) & 0xff;
                                uint b = (rgba >> 16) & 0xff;

                                uint i = ((r + g + b) / 3) & 0xff;
                                uint a = (rgba >> 24) & 0xff;

                                newpixel = (byte)((((i * 15) / 255) & 0xf) | (((a * 15) / 255) << 4));
                            }

                            output[inp++] = newpixel;
                        }
                    }
                }
            }

            return output;
        }
        #endregion

        #region IA8
        private byte[] fromIA8(byte[] tpl, int width, int height)
        {
            uint[] output = new uint[width * height];
            int inp = 0;

            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 4)
                {
                    for (int y1 = y; y1 < y + 4; y1++)
                    {
                        for (int x1 = x; x1 < x + 4; x1++)
                        {
                            int pixel = Shared.Swap(BitConverter.ToUInt16(tpl, inp++ * 2));

                            if (y1 >= height || x1 >= width)
                                continue;

                            uint a = (uint)(pixel >> 8);
                            uint i = (uint)(pixel & 0xff);

                            output[y1 * width + x1] = (i << 0) | (i << 8) | (i << 16) | (a << 24);
                        }
                    }
                }
            }

            return Shared.UIntArrayToByteArray(output);
        }

        private byte[] toIA8(Bitmap img)
        {
            uint[] pixeldata = imageToRgba(img);
            int w = img.Width;
            int h = img.Height;
            int inp = 0;
            byte[] output = new byte[Shared.AddPadding(w, 4) * Shared.AddPadding(h, 4) * 2];

            for (int y1 = 0; y1 < h; y1 += 4)
            {
                for (int x1 = 0; x1 < w; x1 += 4)
                {
                    for (int y = y1; y < y1 + 4; y++)
                    {
                        for (int x = x1; x < x1 + 4; x++)
                        {
                            ushort newpixel;

                            if (x >= w || y >= h)
                                newpixel = 0;
                            else
                            {
                                uint rgba = pixeldata[x + (y * w)];

                                uint r = (rgba >> 0) & 0xff;
                                uint g = (rgba >> 8) & 0xff;
                                uint b = (rgba >> 16) & 0xff;

                                uint i = ((r + g + b) / 3) & 0xff;
                                uint a = (rgba >> 24) & 0xff;

                                newpixel = (ushort)((a << 8) | i);
                            }

                            byte[] temp = BitConverter.GetBytes(newpixel);
                            Array.Reverse(temp);

                            output[inp++] = (byte)(newpixel >> 8);
                            output[inp++] = (byte)(newpixel & 0xff);
                        }
                    }
                }
            }

            return output;
        }
        #endregion

        #region CI4
        private byte[] fromCI4(byte[] tpl, uint[] paletteData, int width, int height)
        {
            uint[] output = new uint[width * height];
            int i = 0;

            for (int y = 0; y < height; y += 8)
            {
                for (int x = 0; x < width; x += 8)
                {
                    for (int y1 = y; y1 < y + 8; y1++)
                    {
                        for (int x1 = x; x1 < x + 8; x1 += 2)
                        {
                            byte pixel = tpl[i++];

                            if (y1 >= height || x1 >= width)
                                continue;

                            output[y1 * width + x1] = paletteData[pixel >> 4]; ;
                            if (y1 * width + x1 + 1 < output.Length) output[y1 * width + x1 + 1] = paletteData[pixel & 0x0F];
                        }
                    }
                }
            }

            return Shared.UIntArrayToByteArray(output);
        }

        //toCI4 done in class ColorIndexConverter
        #endregion

        #region CI8
        private byte[] fromCI8(byte[] tpl, uint[] paletteData, int width, int height)
        {
            uint[] output = new uint[width * height];
            int i = 0;

            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 8)
                {
                    for (int y1 = y; y1 < y + 4; y1++)
                    {
                        for (int x1 = x; x1 < x + 8; x1++)
                        {
                            ushort pixel = tpl[i++];

                            if (y1 >= height || x1 >= width)
                                continue;

                            output[y1 * width + x1] = paletteData[pixel];
                        }
                    }
                }
            }

            return Shared.UIntArrayToByteArray(output);
        }

        //toCI8 done in class ColorIndexConverter
        #endregion

        #region CI14X2
        private byte[] fromCI14X2(byte[] tpl, uint[] paletteData, int width, int height)
        {
            uint[] output = new uint[width * height];
            int i = 0;

            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 4)
                {
                    for (int y1 = y; y1 < y + 4; y1++)
                    {
                        for (int x1 = x; x1 < x + 4; x1++)
                        {
                            ushort pixel = Shared.Swap(BitConverter.ToUInt16(tpl, i++ * 2));

                            if (y1 >= height || x1 >= width)
                                continue;

                            output[y1 * width + x1] = paletteData[pixel & 0x3FFF];
                        }
                    }
                }
            }

            return Shared.UIntArrayToByteArray(output);
        }

        //toCI14X2 done in class ColorIndexConverter
        #endregion

        #region CMP
        private byte[] fromCMP(byte[] tpl, int width, int height)
        {
            uint[] output = new uint[width * height];
            ushort[] c = new ushort[4];
            int[] pix = new int[4];
            int inp = 0;

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    int ww = Shared.AddPadding(width, 8);

                    int x0 = x & 0x03;
                    int x1 = (x >> 2) & 0x01;
                    int x2 = x >> 3;

                    int y0 = y & 0x03;
                    int y1 = (y >> 2) & 0x01;
                    int y2 = y >> 3;

                    int off = (8 * x1) + (16 * y1) + (32 * x2) + (4 * ww * y2);

                    c[0] = Shared.Swap(BitConverter.ToUInt16(tpl, off));
                    c[1] = Shared.Swap(BitConverter.ToUInt16(tpl, off + 2));

                    if (c[0] > c[1])
                    {
                        c[2] = (ushort)avg(2, 1, c[0], c[1]);
                        c[3] = (ushort)avg(1, 2, c[0], c[1]);
                    }
                    else
                    {
                        c[2] = (ushort)avg(1, 1, c[0], c[1]);
                        c[3] = 0;
                    }

                    uint pixel = Shared.Swap(BitConverter.ToUInt32(tpl, off + 4));

                    int ix = x0 + (4 * y0);
                    int raw = c[(pixel >> (30 - (2 * ix))) & 0x03];

                    pix[0] = (raw >> 8) & 0xf8;
                    pix[1] = (raw >> 3) & 0xf8;
                    pix[2] = (raw << 3) & 0xf8;
                    pix[3] = 0xff;
                    if (((pixel >> (30 - (2 * ix))) & 0x03) == 3 && c[0] <= c[1]) pix[3] = 0x00;

                    output[inp] = (uint)((pix[0] << 16) | (pix[1] << 8) | (pix[2] << 0) | (pix[3] << 24));
                    inp++;
                }
            }

            return Shared.UIntArrayToByteArray(output);
        }

        //There's currently no conversion to CMP
        #endregion
        #endregion

        #region Events
        /// <summary>
        /// Fires debugging messages. You may write them into a log file or log textbox.
        /// </summary>
        public event EventHandler<MessageEventArgs> Debug;

        private void fireDebug(string debugMessage, params object[] args)
        {
            EventHandler<MessageEventArgs> debug = Debug;
            if (debug != null)
                debug(new object(), new MessageEventArgs(string.Format(debugMessage, args)));
        }
        #endregion
    }

    public class TPL_Header
    {
        private uint tplMagic = 0x0020AF30;
        private uint numOfTextures = 0;
        private uint headerSize = 0x0C;

        public uint TplMagic { get { return tplMagic; } }
        public uint NumOfTextures { get { return numOfTextures; } set { numOfTextures = value; } }
        public uint HeaderSize { get { return headerSize; } }

        public void Write(Stream writeStream)
        {
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(tplMagic)), 0, 4);
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(numOfTextures)), 0, 4);
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(headerSize)), 0, 4);
        }
    }

    public class TPL_TextureEntry
    {
        private uint textureHeaderOffset = 0x00000000;
        private uint paletteHeaderOffset = 0x00000000;

        public uint TextureHeaderOffset { get { return textureHeaderOffset; } set { textureHeaderOffset = value; } }
        public uint PaletteHeaderOffset { get { return paletteHeaderOffset; } set { paletteHeaderOffset = value; } }

        public void Write(Stream writeStream)
        {
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(textureHeaderOffset)), 0, 4);
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(paletteHeaderOffset)), 0, 4);
        }
    }

    public class TPL_TextureHeader
    {
        private ushort textureHeight;
        private ushort textureWidth;
        private uint textureFormat;
        private uint textureDataOffset;
        private uint wrapS = 0x00000000;
        private uint wrapT = 0x00000000;
        private uint minFilter = 0x00000001;
        private uint magFilter = 0x00000001;
        private uint lodBias = 0x00000000;
        private byte edgeLod = 0x00;
        private byte minLod = 0x00;
        private byte maxLod = 0x00;
        private byte unpacked = 0x00;

        public ushort TextureHeight { get { return textureHeight; } set { textureHeight = value; } }
        public ushort TextureWidth { get { return textureWidth; } set { textureWidth = value; } }
        public uint TextureFormat { get { return textureFormat; } set { textureFormat = value; } }
        public uint TextureDataOffset { get { return textureDataOffset; } set { textureDataOffset = value; } }
        public uint WrapS { get { return wrapS; } set { wrapS = value; } }
        public uint WrapT { get { return wrapT; } set { wrapT = value; } }
        public uint MinFilter { get { return minFilter; } set { minFilter = value; } }
        public uint MagFilter { get { return magFilter; } set { magFilter = value; } }
        public uint LodBias { get { return lodBias; } set { lodBias = value; } }
        public byte EdgeLod { get { return edgeLod; } set { edgeLod = value; } }
        public byte MinLod { get { return minLod; } set { minLod = value; } }
        public byte MaxLod { get { return maxLod; } set { maxLod = value; } }
        public byte Unpacked { get { return unpacked; } set { unpacked = value; } }

        public void Write(Stream writeStream)
        {
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(textureHeight)), 0, 2);
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(textureWidth)), 0, 2);
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(textureFormat)), 0, 4);
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(textureDataOffset)), 0, 4);
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(wrapS)), 0, 4);
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(wrapT)), 0, 4);
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(minFilter)), 0, 4);
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(magFilter)), 0, 4);
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(lodBias)), 0, 4);
            writeStream.WriteByte(edgeLod);
            writeStream.WriteByte(minLod);
            writeStream.WriteByte(maxLod);
            writeStream.WriteByte(unpacked);
        }
    }

    public class TPL_PaletteHeader
    {
        private ushort numberOfItems = 0x0000;
        private byte unpacked = 0x00;
        private byte pad = 0x00;
        private uint paletteFormat = 255;
        private uint paletteDataOffset;

        public ushort NumberOfItems { get { return numberOfItems; } set { numberOfItems = value; } }
        public byte Unpacked { get { return unpacked; } set { unpacked = value; } }
        public byte Pad { get { return pad; } set { pad = value; } }
        public uint PaletteFormat { get { return paletteFormat; } set { paletteFormat = value; } }
        public uint PaletteDataOffset { get { return paletteDataOffset; } set { paletteDataOffset = value; } }

        public void Write(Stream writeStream)
        {
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(numberOfItems)), 0, 2);
            writeStream.WriteByte(unpacked);
            writeStream.WriteByte(pad);
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(paletteFormat)), 0, 4);
            writeStream.Write(BitConverter.GetBytes(Shared.Swap(paletteDataOffset)), 0, 4);
        }
    }

    internal class ColorIndexConverter
    {
        private uint[] rgbaPalette;
        private byte[] tplPalette;
        private uint[] rgbaData;
        private byte[] tplData;
        private TPL_TextureFormat tplFormat;
        private TPL_PaletteFormat paletteFormat;
        private int width;
        private int height;

        public byte[] Palette { get { return tplPalette; } }
        public byte[] Data { get { return tplData; } }

        public ColorIndexConverter(uint[] rgbaData, int width, int height, TPL_TextureFormat tplFormat, TPL_PaletteFormat paletteFormat)
        {
            if (tplFormat != TPL_TextureFormat.CI4 && tplFormat != TPL_TextureFormat.CI8) // && tplFormat != TPL_TextureFormat.CI14X2)
                throw new Exception("Texture format must be either CI4 or CI8"); // or CI14X2!");
            if (paletteFormat != TPL_PaletteFormat.IA8 && paletteFormat != TPL_PaletteFormat.RGB565 && paletteFormat != TPL_PaletteFormat.RGB5A3)
                throw new Exception("Palette format must be either IA8, RGB565 or RGB5A3!");

            this.rgbaData = rgbaData;
            this.width = width;
            this.height = height;
            this.tplFormat = tplFormat;
            this.paletteFormat = paletteFormat;

            buildPalette();

            if (tplFormat == TPL_TextureFormat.CI4) toCI4();
            else if (tplFormat == TPL_TextureFormat.CI8) toCI8();
            else toCI14X2();
        }

        #region Private Functions
        private void toCI4()
        {
            byte[] indexData = new byte[libWiiSharp.Shared.AddPadding(width, 8) * libWiiSharp.Shared.AddPadding(height, 8) / 2];
            int i = 0;

            for (int y = 0; y < height; y += 8)
            {
                for (int x = 0; x < width; x += 8)
                {
                    for (int y1 = y; y1 < y + 8; y1++)
                    {
                        for (int x1 = x; x1 < x + 8; x1 += 2)
                        {
                            uint pixel;

                            if (y1 >= height || x1 >= width)
                                pixel = 0;
                            else
                                pixel = rgbaData[y1 * width + x1];

                            uint index1 = getColorIndex(pixel);

                            if (y1 >= height || x1 >= width)
                                pixel = 0;
                            else if (y1 * width + x1 + 1 >= rgbaData.Length)
                                pixel = 0;
                            else
                                pixel = rgbaData[y1 * width + x1 + 1];

                            uint index2 = getColorIndex(pixel);

                            indexData[i++] = (byte)(((byte)index1 << 4) | (byte)index2);
                        }
                    }
                }
            }

            this.tplData = indexData;
        }

        private void toCI8()
        {
            byte[] indexData = new byte[libWiiSharp.Shared.AddPadding(width, 8) * libWiiSharp.Shared.AddPadding(height, 4)];
            int i = 0;

            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 8)
                {
                    for (int y1 = y; y1 < y + 4; y1++)
                    {
                        for (int x1 = x; x1 < x + 8; x1++)
                        {
                            uint pixel;

                            if (y1 >= height || x1 >= width)
                                pixel = 0;
                            else
                                pixel = rgbaData[y1 * width + x1];

                            indexData[i++] = (byte)getColorIndex(pixel);
                        }
                    }
                }
            }

            this.tplData = indexData;
        }

        private void toCI14X2()
        {
            byte[] indexData = new byte[libWiiSharp.Shared.AddPadding(width, 4) * libWiiSharp.Shared.AddPadding(height, 4) * 2];
            int i = 0;

            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 4)
                {
                    for (int y1 = y; y1 < y + 4; y1++)
                    {
                        for (int x1 = x; x1 < x + 4; x1++)
                        {
                            uint pixel;

                            if (y1 >= height || x1 >= width)
                                pixel = 0;
                            else
                                pixel = rgbaData[y1 * width + x1];

                            byte[] temp = BitConverter.GetBytes((ushort)getColorIndex(pixel));
                            indexData[i++] = temp[1];
                            indexData[i++] = temp[0];
                        }
                    }
                }
            }

            this.tplData = indexData;
        }

        private void buildPalette()
        {
            int palLength = 256;
            if (tplFormat == TPL_TextureFormat.CI4) palLength = 16;
            else if (tplFormat == TPL_TextureFormat.CI14X2) palLength = 16384;

            List<uint> palette = new List<uint>();
            List<ushort> tPalette = new List<ushort>();

            palette.Add(0);
            tPalette.Add(0);

            for (int i = 1; i < rgbaData.Length; i++)
            {
                if (palette.Count == palLength) break;
                if (((rgbaData[i] >> 24) & 0xff) < ((tplFormat == TPL_TextureFormat.CI14X2) ? 1 : 25)) continue;

                ushort tplValue = libWiiSharp.Shared.Swap(convertToPaletteValue((int)rgbaData[i]));

                if (!palette.Contains(rgbaData[i]) && !tPalette.Contains(tplValue))
                {
                    palette.Add(rgbaData[i]);
                    tPalette.Add(tplValue);
                }
            }

            while (palette.Count % 16 != 0)
            { palette.Add(0xffffffff); tPalette.Add(0xffff); }

            tplPalette = libWiiSharp.Shared.UShortArrayToByteArray(tPalette.ToArray());
            rgbaPalette = palette.ToArray();
        }

        private ushort convertToPaletteValue(int rgba)
        {
            int newpixel = 0, r, g, b, a;

            if (paletteFormat == TPL_PaletteFormat.IA8)
            {
                int intensity = ((((rgba >> 0) & 0xff) + ((rgba >> 8) & 0xff) + ((rgba >> 16) & 0xff)) / 3) & 0xff;
                int alpha = (rgba >> 24) & 0xff;

                newpixel = (ushort)((alpha << 8) | intensity);
            }
            else if (paletteFormat == TPL_PaletteFormat.RGB565)
            {
                newpixel = (ushort)(((((rgba >> 16) & 0xff) >> 3) << 11) | ((((rgba >> 8) & 0xff) >> 2) << 5) | ((((rgba >> 0) & 0xff) >> 3) << 0));
            }
            else
            {
                r = (rgba >> 16) & 0xff;
                g = (rgba >> 8) & 0xff;
                b = (rgba >> 0) & 0xff;
                a = (rgba >> 24) & 0xff;

                if (a <= 0xda) //RGB4A3
                {
                    newpixel &= ~(1 << 15);

                    r = ((r * 15) / 255) & 0xf;
                    g = ((g * 15) / 255) & 0xf;
                    b = ((b * 15) / 255) & 0xf;
                    a = ((a * 7) / 255) & 0x7;

                    newpixel |= a << 12;
                    newpixel |= b << 0;
                    newpixel |= g << 4;
                    newpixel |= r << 8;
                }
                else //RGB5
                {
                    newpixel |= (1 << 15);

                    r = ((r * 31) / 255) & 0x1f;
                    g = ((g * 31) / 255) & 0x1f;
                    b = ((b * 31) / 255) & 0x1f;

                    newpixel |= b << 0;
                    newpixel |= g << 5;
                    newpixel |= r << 10;
                }
            }

            return (ushort)newpixel;
        }

        private uint getColorIndex(uint value)
        {
            uint minDistance = 0x7FFFFFFF;
            uint colorIndex = 0;

            if (((value >> 24) & 0xFF) < ((tplFormat == TPL_TextureFormat.CI14X2) ? 1 : 25)) return 0;
            ushort color = convertToPaletteValue((int)value);

            for (int i = 0; i < rgbaPalette.Length; i++)
            {
                ushort curPal = convertToPaletteValue((int)rgbaPalette[i]);

                if (color == curPal) return (uint)i;
                uint curDistance = getDistance(color, curPal); //(uint)Math.Abs(Math.Abs(color) - Math.Abs(curVal));

                if (curDistance < minDistance)
                {
                    minDistance = curDistance;
                    colorIndex = (uint)i;
                }
            }

            return colorIndex;
        }

        private uint getDistance(ushort color, ushort paletteColor)
        {
            uint curCol = convertToRgbaValue(color);
            uint palCol = convertToRgbaValue(paletteColor);

            uint curA = (curCol >> 24) & 0xFF;
            uint curR = (curCol >> 16) & 0xFF;
            uint curG = (curCol >> 8) & 0xFF;
            uint curB = (curCol >> 0) & 0xFF;

            uint palA = (palCol >> 24) & 0xFF;
            uint palR = (palCol >> 16) & 0xFF;
            uint palG = (palCol >> 8) & 0xFF;
            uint palB = (palCol >> 0) & 0xFF;

            uint distA = Math.Max(curA, palA) - Math.Min(curA, palA);
            uint distR = Math.Max(curR, palR) - Math.Min(curR, palR);
            uint distG = Math.Max(curG, palG) - Math.Min(curG, palG);
            uint distB = Math.Max(curB, palB) - Math.Min(curB, palB);

            return distA + distR + distG + distB;
        }

        private uint convertToRgbaValue(ushort pixel)
        {
            int rgba = 0, r, g, b, a;

            if (paletteFormat == TPL_PaletteFormat.IA8)
            {
                int i = (pixel >> 8);
                a = pixel & 0xff;

                rgba = (i << 0) | (i << 8) | (i << 16) | (a << 24);
            }
            else if (paletteFormat == TPL_PaletteFormat.RGB565)
            {
                b = (((pixel >> 11) & 0x1F) << 3) & 0xff;
                g = (((pixel >> 5) & 0x3F) << 2) & 0xff;
                r = (((pixel >> 0) & 0x1F) << 3) & 0xff;
                a = 255;

                rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24);
            }
            else
            {
                if ((pixel & (1 << 15)) != 0)
                {
                    b = (((pixel >> 10) & 0x1F) * 255) / 31;
                    g = (((pixel >> 5) & 0x1F) * 255) / 31;
                    r = (((pixel >> 0) & 0x1F) * 255) / 31;
                    a = 255;
                }
                else
                {
                    a = (((pixel >> 12) & 0x07) * 255) / 7;
                    b = (((pixel >> 8) & 0x0F) * 255) / 15;
                    g = (((pixel >> 4) & 0x0F) * 255) / 15;
                    r = (((pixel >> 0) & 0x0F) * 255) / 15;
                }

                rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24);
            }

            return (uint)rgba;
        }
        #endregion
    }
}
